//
// System.Drawing.carbonFunctions.cs
//
// Authors:
//      Geoff Norton (gnorton@customerdna.com>
//
// Copyright (C) 2007 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#undef DEBUG_CLIPPING

using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;

namespace System.Drawing {

	internal static class MacSupport {
		internal static Hashtable contextReference = new Hashtable ();
		internal static object lockobj = new object ();

		internal static Delegate hwnd_delegate;

#if DEBUG_CLIPPING
		internal static float red = 1.0f;
		internal static float green = 0.0f;
		internal static float blue = 0.0f;
		internal static int debug_threshold = 1;
#endif

		static MacSupport () {
#if !NETSTANDARD1_6
			foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ()) {
				if (String.Equals (asm.GetName ().Name, "System.Windows.Forms")) {
					Type driver_type = asm.GetType ("System.Windows.Forms.XplatUICarbon");
					if (driver_type != null) {
						hwnd_delegate = (Delegate) driver_type.GetTypeInfo() .GetField ("HwndDelegate", BindingFlags.NonPublic | BindingFlags.Static).GetValue (null);
					}
				}
			}
#endif
		}

		internal static CocoaContext GetCGContextForNSView (IntPtr handle) {
			if (handle == IntPtr.Zero) {
				return null;
			}

			IntPtr focusView = objc_msgSend (objc_getClass ("NSView"), sel_registerName ("focusView"));
			IntPtr focusHandle = IntPtr.Zero;
			if (focusView != handle) {
				if (!bool_objc_msgSend (handle, sel_registerName ("lockFocusIfCanDraw")))
					return null;

				focusHandle = handle;
			}

			IntPtr windowHandle = objc_msgSend (handle, sel_registerName ("window"));
			IntPtr graphicsContext = objc_msgSend (windowHandle, sel_registerName ("graphicsContext"));
			IntPtr ctx = objc_msgSend (graphicsContext, sel_registerName ("graphicsPort"));
			bool isFlipped = bool_objc_msgSend (handle, sel_registerName ("isFlipped"));
			Size size;

			CGContextSaveGState (ctx);

			if (IntPtr.Size == 4) {
				CGRect32 bounds = new CGRect32 ();
				objc_msgSend_stret (ref bounds, handle, sel_registerName ("bounds"));
				if (isFlipped) {
					CGContextTranslateCTM32 (ctx, bounds.origin.x, bounds.size.height);
					CGContextScaleCTM32 (ctx, 1.0f, -1.0f);
				}
				size = new Size ((int) bounds.size.width, (int) bounds.size.height);
			} else {
				CGRect64 bounds = new CGRect64 ();
				objc_msgSend_stret (ref bounds, handle, sel_registerName ("bounds"));
				if (isFlipped) {
					CGContextTranslateCTM64 (ctx, bounds.origin.x, bounds.size.height);
					CGContextScaleCTM64 (ctx, 1.0f, -1.0f);
				}
				size = new Size ((int) bounds.size.width, (int) bounds.size.height);
			}

			return new CocoaContext (focusHandle, ctx, size.Width, size.Height);
		}

		internal static CarbonContext GetCGContextForView (IntPtr handle) {
			IntPtr context = IntPtr.Zero;
			IntPtr port = IntPtr.Zero;
			IntPtr window = IntPtr.Zero;

			if (IntPtr.Size == 8)
				throw new NotSupportedException ();

			window = GetControlOwner (handle);

			if (handle == IntPtr.Zero || window == IntPtr.Zero) {
				// FIXME: Can we actually get a CGContextRef for the desktop?  this makes context IntPtr.Zero
				port = GetQDGlobalsThePort ();
				CreateCGContextForPort (port, ref context);

				CGRect32 desktop_bounds = CGDisplayBounds32 (CGMainDisplayID ());

				return new CarbonContext (port, context, (int)desktop_bounds.size.width, (int)desktop_bounds.size.height);
			}

			QDRect window_bounds = new QDRect ();
			CGRect32 view_bounds = new CGRect32 ();

			port = GetWindowPort (window);
			
			context = GetContext (port);

			GetWindowBounds (window, 32, ref window_bounds);

			HIViewGetBounds (handle, ref view_bounds);

			HIViewConvertRect (ref view_bounds, handle, IntPtr.Zero);
			
			if (view_bounds.size.height < 0) view_bounds.size.height = 0;
			if (view_bounds.size.width < 0) view_bounds.size.width = 0;

			CGContextTranslateCTM32 (context, view_bounds.origin.x, (window_bounds.bottom - window_bounds.top) - (view_bounds.origin.y + view_bounds.size.height));

			// Create the original rect path and clip to it
			CGRect32 rc_clip = new CGRect32 (0, 0, view_bounds.size.width, view_bounds.size.height);

			CGContextSaveGState (context);

			Rectangle [] clip_rectangles = (Rectangle []) hwnd_delegate.DynamicInvoke (new object [] {handle});
			if (clip_rectangles != null && clip_rectangles.Length > 0) {
				int length = clip_rectangles.Length;
				
				CGContextBeginPath (context);
				CGContextAddRect32 (context, rc_clip);

				for (int i = 0; i < length; i++) {
					CGContextAddRect32 (context, new CGRect32 (clip_rectangles [i].X, view_bounds.size.height - clip_rectangles [i].Y - clip_rectangles [i].Height, clip_rectangles [i].Width, clip_rectangles [i].Height));
				}
				CGContextClosePath (context);
				CGContextEOClip (context);
#if DEBUG_CLIPPING
				if (clip_rectangles.Length >= debug_threshold) {
					CGContextSetRGBFillColor (context, red, green, blue, 0.5f);
					CGContextFillRect (context, rc_clip);
					CGContextFlush (context);
					System.Threading.Thread.Sleep (500);
					if (red == 1.0f) { red = 0.0f; blue = 1.0f; } 
					else if (blue == 1.0f) { blue = 0.0f; green = 1.0f; } 
					else if (green == 1.0f) { green = 0.0f; red = 1.0f; } 
				}
#endif
			} else {
				CGContextBeginPath (context);
				CGContextAddRect32 (context, rc_clip);
				CGContextClosePath (context);
				CGContextClip (context);
			}

			return new CarbonContext (port, context, (int)view_bounds.size.width, (int)view_bounds.size.height);
		}

		internal static IntPtr GetContext (IntPtr port) {
			IntPtr context = IntPtr.Zero;

			lock (lockobj) { 
#if FALSE
				if (contextReference [port] != null) {
					CreateCGContextForPort (port, ref context);
				} else {
					QDBeginCGContext (port, ref context);
					contextReference [port] = context;
				}
#else
				CreateCGContextForPort (port, ref context);
#endif
			}

			return context;
		}

		internal static void ReleaseContext (IntPtr port, IntPtr context) {
			CGContextRestoreGState (context);

			lock (lockobj) { 
#if FALSE
				if (contextReference [port] != null && context == (IntPtr) contextReference [port]) { 
					QDEndCGContext (port, ref context);
					contextReference [port] = null;
				} else {
					CFRelease (context);
				}
#else
				CFRelease (context);
#endif
			}
		}

		#region Cocoa Methods
		[DllImport("libobjc.dylib")]
		public static extern IntPtr objc_getClass(string className); 
		[DllImport("libobjc.dylib")]
		public static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector, string argument);  
		[DllImport("libobjc.dylib")]
		public static extern IntPtr objc_msgSend(IntPtr basePtr, IntPtr selector);        
		[DllImport("libobjc.dylib")]
		public static extern void objc_msgSend_stret(ref CGRect32 arect, IntPtr basePtr, IntPtr selector);        
		[DllImport("libobjc.dylib")]
		public static extern void objc_msgSend_stret(ref CGRect64 arect, IntPtr basePtr, IntPtr selector);        
		[DllImport ("libobjc.dylib", EntryPoint = "objc_msgSend")]
		public static extern bool bool_objc_msgSend (IntPtr handle, IntPtr selector);
		[DllImport ("libobjc.dylib", EntryPoint = "objc_msgSend")]
		public static extern bool bool_objc_msgSend_IntPtr (IntPtr handle, IntPtr selector, IntPtr argument);
		[DllImport("libobjc.dylib")]
		public static extern IntPtr sel_registerName(string selectorName);         
		#endregion

		[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern IntPtr CGMainDisplayID ();
		[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGDisplayBounds")]
		internal static extern CGRect32 CGDisplayBounds32 (IntPtr display);

		[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern int HIViewGetBounds (IntPtr vHnd, ref CGRect32 r);
		[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern int HIViewConvertRect (ref CGRect32 r, IntPtr a, IntPtr b);

		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern IntPtr GetControlOwner (IntPtr aView);

		[DllImport("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern int GetWindowBounds (IntPtr wHnd, uint reg, ref QDRect rect);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern IntPtr GetWindowPort (IntPtr hWnd);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern IntPtr GetQDGlobalsThePort ();
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CreateCGContextForPort (IntPtr port, ref IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CFRelease (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void QDBeginCGContext (IntPtr port, ref IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void QDEndCGContext (IntPtr port, ref IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGContextTranslateCTM")]
		internal static extern void CGContextTranslateCTM32 (IntPtr context, float tx, float ty);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGContextScaleCTM")]
		internal static extern void CGContextScaleCTM32 (IntPtr context, float x, float y);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGContextTranslateCTM")]
		internal static extern void CGContextTranslateCTM64 (IntPtr context, double tx, double ty);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGContextScaleCTM")]
		internal static extern void CGContextScaleCTM64 (IntPtr context, double x, double y);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextFlush (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextSynchronize (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern IntPtr CGPathCreateMutable ();
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon", EntryPoint = "CGContextAddRect")]
		internal static extern void CGContextAddRect32 (IntPtr context, CGRect32 rect);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextBeginPath (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextClosePath (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextAddPath (IntPtr context, IntPtr path);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextClip (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextEOClip (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextEOFillPath (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextSaveGState (IntPtr context);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextRestoreGState (IntPtr context);

#if DEBUG_CLIPPING
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextSetRGBFillColor (IntPtr context, float red, float green, float blue, float alpha);
		[DllImport ("/System/Library/Frameworks/Carbon.framework/Versions/Current/Carbon")]
		internal static extern void CGContextFillRect (IntPtr context, CGRect32 rect);
#endif
	}

	internal struct CGSize32 {
		public float width;
		public float height;
	}

	internal struct CGPoint32 {
		public float x;
		public float y;
	}

	internal struct CGRect32 {
		public CGRect32 (float x, float y, float width, float height) {
			this.origin.x = x;
			this.origin.y = y;
			this.size.width = width;
			this.size.height = height;
		}

		public CGPoint32 origin;
		public CGSize32 size;
	}

	internal struct CGSize64 {
		public double width;
		public double height;
	}

	internal struct CGPoint64 {
		public double x;
		public double y;
	}

	internal struct CGRect64 {
		public CGRect64 (double x, double y, double width, double height) {
			this.origin.x = x;
			this.origin.y = y;
			this.size.width = width;
			this.size.height = height;
		}

		public CGPoint64 origin;
		public CGSize64 size;
	}

	internal struct QDRect
	{
		public short top;
		public short left;
		public short bottom;
		public short right;
	}

	internal struct CarbonContext : IMacContext
	{
		public IntPtr port;
		public IntPtr ctx;
		public int width;
		public int height;

		public CarbonContext (IntPtr port, IntPtr ctx, int width, int height)
		{
			this.port = port;
			this.ctx = ctx;
			this.width = width;
			this.height = height;
		}

		public void Synchronize ()
		{
			MacSupport.CGContextSynchronize (ctx);
		}

		public void Release ()
		{
			MacSupport.ReleaseContext (port, ctx);
		}
	}

	internal class CocoaContext : IMacContext
	{
		public IntPtr focusHandle;
		public IntPtr ctx;
		public int width;
		public int height;

		public CocoaContext (IntPtr focusHandle, IntPtr ctx, int width, int height)
		{
			this.focusHandle = focusHandle;
			this.ctx = ctx;
			this.width = width;
			this.height = height;
		}

		public void Synchronize ()
		{
			MacSupport.CGContextSynchronize (ctx);
		}

		public void Release ()
		{
			if (IntPtr.Zero != focusHandle)
				MacSupport.CGContextFlush (ctx);

			MacSupport.CGContextRestoreGState (ctx);

			if (IntPtr.Zero != focusHandle)
				MacSupport.objc_msgSend (focusHandle, MacSupport.sel_registerName ("unlockFocus"));
		}
	}

	internal interface IMacContext
	{
		void Synchronize ();
		void Release ();
	}
}
